Глава 11
ДРУГИЕ ВОЗМОЖНОСТИ ТУРБО ПАСКАЛЯ
11.1. ВНЕШНИЕ ПРОЦЕДУРЫ (ФУНКЦИИ)
С помощью внешних процедур (функций) можно осуществить вызов из программы процедур или функций, написанных на языке ассемблера. Ассемблер обеспечивает компиляцию программ, написанных на машинно-ориентированном языке программирования низкого уровня. В Турбо Паскале есть собственный встроенный ассемблер см. гл.12). В этом разделе речь идет о программах, написанных и откомпилированных с помощью внешнего ассемблера, такого как, например, ассемблер фирмы MicroSoft или Turbo Assembler фирмы Borland.
Машинно-ориентированный язык ассемблера предоставляет квалифицированному программисту богатейшие возможности использования всех особенностей архитектуры ПК. Ассемблерные программы выполняются значительно быстрее и занимают меньший объем памяти, чем программы, написанные на Турбо Паскале, однако низкий уровень языка ассемблера существенно снижает производительность труда программиста и резко усложняет отладку программ. Как правило, на языке ассемблера пишутся сравнительно небольшие фрагменты программ, в которых используются недоступные из Турбо Паскаля особенности архитектуры ПК.
Внешняя процедура (функция) в программе, написанной на Турбо Паскале, объявляется своим заголовком, за которым следует стандартная директива EXTERNAL, например:
Function LoCase (ch : char):char; external;
Procedure Swapping (var a,b; N:word); external;
Как видно из этих примеров, тело внешней процедуры (функции) отсутствует - его заменяет директива EXTERNAL. Для подключения ассемблерной программы необходимо предварительно ее откомпилировать и получить объектный файл с расширением OBJ, содержащий перемещаемый код ассемблерной программы. Непосредственно перед описанием внешней процедуры (функции) в тело основной программы вставляется директива компилятора {$L<имя файла>}, где <имя фата> - имя OBJ-файла. Диск и каталог, в котором следует искать этот файл, если он не обнаружен в текущем каталоге, указываются опцией OPTIONS/DIRECTORIES/OBJECT DIRECTORIES (см.прил.1).
Перед передачей управления внешней процедуре (функции) программа помещает параметры обращения в программный стек в том порядке, как они перечислены в заголовке процедуры (функции). Ассемблерная процедура должна сохранить регистры ВР, SP, SS и DS центрального процессора в самом начале своей работы и восстановить содержимое этих регистров перед возвратом управления в программу. Остальные регистры можно не сохранять и соответственно не восстанавливать.
Параметры могут передаваться по ссылке или по значению. Если параметр передается по ссылке, в стек помещается указатель, содержащий абсолютный адрес параметра, если по значению - в стек помещается сам параметр, точнее - его значение. Все параметры-переменные, т.е. параметры, объявленные в заголовке с предшествующим словом VAR, всегда передаются по ссылке. Параметры-значения могут передаваться по ссылке или по значению в зависимости от длины внутреннего представления соответствующего параметра. В общем случае используется следующее правило: если длина внутреннего представления параметра-значения составляет 1, 2 или 4 байта, он передается своим значением, т.е. его значение помещается в стек. Точно так же через стек передаются и все вещественные данные длиной в 4, 6, 8 и 10 байт (в версии 4.0 эти данные передаются через стек сопроцессора 8087/80287, начиная с версии 5.0 -через стек центрального процессора 8086/80486). Во всех остальных случаях, если длина внутреннего представления больше 4 байт, соответствующий параметр передается по ссылке.
Ассемблерные функции в зависимости от длины внутреннего представления результата должны возвращать его через регистры центрального процессора или сопроцессора по следующим правилам:
Все ассемблерные процедуры должны размещаться в сегменте с именем CODE или CSEG, или с именем, оканчивающимся на _ТЕХТ; инициализированные локальные Переменные помещаются в сегмент с именем CONST или с именем, оканчивающимся на _DATA. Все другие локальные переменные необходимо размещать в сегменте с именем DATA или DSEG, или с именем, оканчивающимся на _BSS. Любые другие объявления сегментов игнорируются. Все имена, объявленные в интерфейсной части модулей программы, написанной на Турбо Паскале, становятся доступны ассемблерной процедуре (функции) после их объявления директивой EXTRN. Точно так же все имена ассемблерных процедур и функций, которые должны быть доступны программе на Турбо Паскале, следует объявлять директивой PUBLIC.
11.2. ИСПОЛЬЗОВАНИЕ ВСТРОЕННЫХ МАШИННЫХ КОДОВ
В Турбо Паскале имеется возможность непосредственного включения в программу небольших фрагментов, написанных в машинных кодах. Для этого используется стандартная директива INLINE, за которой в круглых скобках следует один или несколько элементов машинного кода, разделяемых косыми чертами. Элемент кода, в свою очередь, строится из одного или более элементов данных, разделенных знаками «+» или «-»
В качестве элемента данных может использоваться целая константа, идентификатор (переменной, константы или функции) или ссылка на счетчик адреса («*»). Каждый элемент данных вызывает генерацию 1 или 2 байт кода программы. Значение этого кода получается сложением или вычитанием элементов данных в соответствии с разделяющим их знаком. Значением идентификатора переменной, константы, функции служит адрес соответствующего объекта, значением ссылки на счетчик адреса является тот адрес, по которому будет размещаться следующий байт кода.
Элемент кода будет генерировать 1 байт кода, если этот элемент состоит только из целых констант и значение результата не превышает мощности одного байта, т.е. находится в диапазоне от 0 до 255. Если значение превышает 255 или элемент кода содержит ссылку на счетчик адреса, генерируются 2 байта. Знаки «<» и «>» могут использоваться -для отмены автоматического выбора размера генерируемого кода. Если элемент кода начинается со знака «<», в код заносится только 1 байт (с младшей значимостью), даже если само значение занимает 2 байта. Наоборот, если элемент начинается со знака «>», в код заносятся 2 байта (старший байт может оказаться нулевым).
Значением идентификатора является смещение соответствующего объекта. Если переменная - глобальная, смещение задается относительно сегмента данных, хранящееся в регистре DS, если это локальная переменная, - относительно сегмента стека регистр SP). Базовым сегментом типизированной константы является сегмент кода регистр CS).
В следующем примере приводятся две короткие процедуры, с помощью которых можно ввести или вывести данные через любой порт ПК.
Function InPort(Port: Word): Word;
var
pp: Word;
cc:Char;
egin
pp:=port;
inline (
$8b/$96/pp/ { mov DX,pp[bp] }
$EC/ { IN AX,DX }
$88/$86/cc); {mov cc[bp],AX}
InPort:=ord(cc);
end;
Procedure OutPort(Port,Bt: Word);
var
pp: Word;
cc:Char;
begin
pp:=port;
cc:=chr(Bt);
inline (
$8a/$86/cc/ { mov AX,cc[bp] }
$8b/$96/pp/ { mov DX,pp[bp] }
$EE) { OUT DX,AX }
end;
Операторы INLINE могут произвольным образом смешиваться с другими операторами Турбо Паскаля, однако при выходе из процедуры (функции) содержимое регистров ВР, SP, DS и SS должно быть таким же, как и при входе в нее.
С помощью директивы INLINE можно также задавать последовательность машинных кодов, которую необходимо несколько раз вставить в программу. Для этого используется описание INLINE-процедуры, например:
Procedure DisableInterrupts;
inline ($FA); {CLI}
INLINE-процедура имеет обычный для Турбо Паскаля заголовок, в то время как тело процедуры пишется целиком с помощью оператора INLINE. Всякий раз, когда в программе будет встречаться оператор вызова INLINE-процедуры, компилятор Турбо Паскаля будет вставлять на это место не код вызова процедуры, а нужные машинные коды. Например, вместо вызова процедуры в операторе
DisableInterrupt;
компилятор вставит команду запрета прерываний CLI. Таким образом, INLINE-процедуры служат своеобразным средством расширения возможностей стандартного компилятора Турбо Паскаля и подобны макросам ассемблера. Использование INLINE-процедур увеличивает скорость исполнения программы, так как не осуществляется генерация (и исполнение) команд передачи управления в процедуру. По этой причине в INLINE-процедурах не следует использовать команды выхода из подпрограммы. INLINЕ-процедура может иметь параметры, однако на них нельзя ссылаться в INLINE-директивах (на другие символы Турбо Паскаля ссьшаться можно). В следующем примере перемножаются два числа типа INTEGER, результат имеет тип LONGINT:
FUNCTION LongMul(X,YInteger) : Longint;
inline (
$5 А/ {POP AX; получить в АХ число Х }
$58/ { POP DX; получить в DX число Y }
$F7/$EA); { IMUL DX; DX:AX ;= X * Y }
Отметим, что в силу упоминавшегося сходства с макросами ассемблера, имена INLINE-подпрограмм не могут использоваться в качестве аргументов в операторах @ или служить параметрами функций ADDR, OFS и SEG.
11.3. ОБРАЩЕНИЕ К ФУНКЦИЯМ ОПЕРАЦИОННОЙ СИСТЕМЫ
Турбо Паскаль предоставляет программисту практически неограниченные возможности использования любых функций стандартной операционной системы MS-DOS. При внимательном анализе материала этой книги Вы, очевидно, заметите, что значительную его часть составляет описание многочисленных библиотечных процедур и функций. Собственно язык Паскаль весьма прост и лаконичен, что, по мнению многих специалистов, и послужило одной из причин его широкого распространения. Библиотечные же процедуры и функции, в своей значительной части, являются, по существу, своеобразным интерфейсом между языковыми средствами Турбо Паскаля и функциями операционной системы. Разумеется, можно только приветствовать усилия разработчиков Турбо Паскаля по созданию мощных библиотек TURBO.TPL и GRAPH.TPU, однако ясно, что таким способом невозможно запрограммировать все допустимые обращения к средствам ДОС. Вот почему в Турбо Паскаль включены две процедуры, с помощью которых программист может сам сформировать вызов той или иной функции дисковой операционной системы (ДОС).
Следует учесть, что единственным механизмом обращения к функциям ДОС является инициация программного прерывания. Прерывание - это особое состояние вычислительного процесса. В момент прерывания нарушается нормальный порядок выполнения команд программы и управление передается специальной процедуре, которая входит в состав ДОС и называется процедурой обработки прерывания. Каждое прерывание характеризуется в рамках ДОС порядковым номером и связано со своей процедурой обработки. В архитектуре центрального процессора ПК предусмотрены прерывания двух типов - аппаратные и программные. Аппаратные прерывания создаются схемами контроля и управления ПК и сигнализируют операционной системе о переходе какого-либо устройства в новое состояние или о возникновении неисправности. Программные прерывания инициируются при выполнении одной из двух специальных команд микропроцессора (INT или INTO) и служат для обращения к средствам ДОС.
Описываемые ниже процедуры входят в состав библиотечного модуля DOS.TPU и становятся доступными после объявления USES DOS. При возникновении программного прерывания в большинстве случаев необходимо передать процедуре обработки прерывания некоторые параметры, в которых конкретизируется запрос нужной функции. Эти параметры, а также выходная информация (результат обработки прерывания) передаются от программы к процедуре и обратно через регистры центрального процессора. В составе модуля DOS.TPU для этих целей определен специальный тип:
type
Registers = record case integer of
0 : (AX, BX, CX, BP, SI, DI, DS, ES, Flags : word);
1 : (AL, AH, BL, BH, CL, CH, DL, DH : byte)
end ;
Этот тип имитирует регистры центрального процессора и дает возможность обращаться к ним как к 16-битным или 8-битным регистрам.
Процедура INTR. С помощью этой процедуры инициируется программное прерывание с требуемым номером. Обращение:
INTR (<N>,<регистры>)
Здесь <N> - выражение типа BYTE; номер прерывания;
<регистры> - переменная типа REGISTERS; в этой переменной процедуре обработки прерывания передается содержимое регистров и в ней же возвращается выходная информация.
Например, прерывание с номером 18 ($12) возвращает в регистре АХ объем оперативной памяти ПК. Короткая программа, представленная в примере 11.1, выведет на экран сообщение об этом объеме.
Пример 11.1.
Uses DOS;
var
r : registers;
begin
Intr ($12, r);
writeln ('Объем памяти = ',r.AX, ' Кбайт')
end.
Процедура MSDOS. Инициирует прерывание с номером 33 ($21). Формат обращения:
MSDOS (<регистры>)
Программное прерывание с номером 33 ($21) стоит особняком, так как оно дает доступ к большому количеству функций ДОС (этим прерыванием вызывается 85 функций). Рассматриваемая процедура полностью эквивалентна вызову процедуры INTR с номером прерывания 33. Например, следующая программа (пример 11.2) выведет на экран версию операционной системы:
Пример 11.2
Uses DOS;
var
r : registers;
begin
r.АН := $30;
MsDos(r);
WriteLn('Версия операционной системы: ', r.AL, '.' r.АН)
end.
11.4. ПОДДЕРЖКА ПРОЦЕДУР ОБРАБОТКИ ПРЕРЫВАНИЙ
При написании процедур обработки прерываний существенными являются два обстоятельства. Во-первых, процедура обработки прерывания не должна искажать работу прерванной программы. Для этого необходимо сначала сохранить регистры центрального процессора, а перед выходом из процедуры - восстановить их. Во-вторых, процедура должна строиться по принципу реентерабельности (повторной входимости): ее работа может быть прервана в любой момент другими прерываниями и ДОС может обратиться к соответствующей функции до завершения обработки предыдущего прерывания.
Турбо Паскаль предоставляет программисту возможность написания процедур обработки прерывания на языке высокого уровня, хотя обычно такие процедуры пишутся на языке ассемблера.
Процедура обработки прерывания, написанная на Турбо Паскале, должна начинаться стандартной директивой INTERRUPT (прерывание), например:
Procedure IntProc (Flags, CS, IP, AX, BX, CX, DX,
SI, DF, DS, ES, BP : word); inerrupt;
begin
...
end ;
Формальные параметры в заголовке процедуры должны перечисляться в указанном порядке - через эти параметры все регистры прерванной программы становятся доступны процедуре обработки прерывания. Количество перечисляемых в заголовке процедуры параметров-регистров может быть любым, но не больше 12. Если в списке опущен какой-либо параметр, должны быть опущены также и все предшествующие ему параметры. Например, описание
Procedure IntProc(SI, DP, ES: word); interrupt;
будет неверным (опущены параметры DS и ВР); правильное описание:
Procedure IntProc(SI, DP, DS, ES, BP: word); interrupt;
Заметим, что компилятор не контролирует порядок перечисления параметров в заголовке процедуры обработки прерывания.
Директива INTERRUPT вызывает генерацию специальных машинных кодов, обеспечивающих заталкивание регистров в стек при входе в процедуру и извлечение их из стека перед выходом из нее.
При входе в процедуру:
push |
ax |
|
push |
bx |
|
push |
cx |
|
push |
dx |
|
push |
si |
|
push |
di |
|
push |
ds |
|
push |
es |
|
push |
bp |
|
mov |
bp, |
si |
sub |
sp, |
LocalSize |
mov |
ax, |
SEG DATA |
mov |
ds, |
ax |
При выходе из процедуры:
mov sp, bp
pop bp
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
irep
В самой процедуре обработки прерывания не рекомендуется обращаться к другим функциям ДОС, так как некоторые из них, в том числе все функции ввода-вывода, нереентерабельны.
Для связи с любыми процедурами прерываний, а следовательно, и с процедурами, написанными программистом, используются векторы прерываний - четырехбайтные абсолютные адреса точек входа в эти процедуры. Векторы прерываний располагаются в младших адресах оперативной памяти, начиная с нулевого адреса: прерывание номер 0 - по адресу 0, номер 1 - по адресу 1*4 = 4, номер N - по адресу N * 4. С помощью следующих двух процедур программист может прочитать содержимое любого вектора или установить его новое значение.
Процедура GETINTVEC. Возвращает вектор прерывания с указанным номером. Обращение:
GETINTVEC (<,<вектор>)
Здесь <D> - выражение типа BYTE; номер прерывания;
<вектор> - переменная типа POINTER; адрес точки входа в процедуру обработки прерывания.
Представленная в примере 11.3 программа выводит на экран содержимое всех ненулевых векторов прерываний.
Пример 11.3
Uses DOS;
var
i : byte; p : pointer;
begin
for i := 0 to 255 do
begin
GetlntVec (i, p) ;
if (Seg (р^) <> 0) or (Ofs (рл) <> 0) then
WriteLn (' N=', i:3, ' Seg=', Seg (р^):5,
' Ofs =' , Ofs (р^) :5)
end
end.
Процедура SETINTVEC. Устанавливает ндвое значение вектора прерывания. Формат обращения:
SETINTVEC (<,<адрес>)
Здесь <D> - выражение типа BYTE; номер прерывания;
<адрес> - выражение типа POINTER; адрес точки входа в процедуру обработки прерывания.
При нормальном завершении программы она выгружается из памяти, что делает невозможным разработку резидентных в памяти процедур обработки прерываний. Вы можете прекратить работу программы и оставить ее резидентной в памяти, если воспользуетесь процедурой KEEP.
Процедура KEEP. Завершает работу программы и оставляет ее резидентной в памяти. Обращение:
KEEP (<код>)
Здесь <код> - выражение типа WORD - код завершения программы. Код завершения представляет собой фактически единственный механизм передачи сообщений от запущенной программы к программе, которая ее запустила. Он может быть проанализирован в вызывающей программе с помощью функции DOSEXITCODE.
Функция DOSEXITCODE. Возвращает значение типа WORD - код завершения подчиненной программы. Обращение:
DOSEXITCODE
Из программы, написанной на Турбо Паскале, можно запустить любую другую готовую к работе программу. Для этого используется процедура ЕХЕС из библиотечного модуля DOS. Формат обращения к процедуре:
ЕХЕС (<имя>,<параметры>)
Здесь <имя> - выражение типа STRING; имя файла с вызываемой программой; <параметры> - выражение типа STRING; параметры вызова.
Имени запускаемой программы может предшествовать путь к файлу. Параметры передаются запускаемой программе в виде текстовой строки и могут быть проанализированы ею с помощью двух следующих функций.
Функция PARAMCOUNT. Возвращает общее количество параметров вызова программы (значение типа WORD). Обращение:
PARAMCOUNT
Параметры вызова обычно следуют в командной строке ДОС сразу за именем вызываемой программы и отделяются от этого имени и друг от друга пробелами, например:
C:\TP\TURBO MYPROG.PAS
C:\SIAM A:\SYSTEM1.SIA
Здесь MYPROG.PAS и A:\SYSTEM1.SIA - параметры, передаваемые программам TURBO и SIAM.
При вызове программы непосредственно из среды Турбо Паскаля ей можно передать параметры с помощью опции OPTIONS/PARAMETERS (см. прил.1).
Функция PARAMSTR. Возвращает значение типа STRING, соответствующее нужному параметру вызова. Формат обращения:
PARAMSTR (<D>)
Здесь <D> - выражение типа WORD; порядковый номер параметра.
Заметим, что программе всегда передается параметр, соответствующий N = 0. В этом параметре ДОС сообщает полное имя запущенной программы с указанием диска и каталога, откуда она была загружена.
Использование процедуры ЕХЕС имеет ряд особенностей. Прежде всего необходимо отметить, что сама вызывающая программа остается резидентной в памяти, поэтому она не должна занимать всю оперативную память. Объем выделяемой программе памяти регулируется опцией OPTIONS/MEMORY SIZES (см. прил.1). По умолчанию параметры LOW HEAP LIMIT и HIGH HEAP LIMIT этой опции таковы (соответственно 0 и 655360 байт), что вызывающая программа, написанная на Турбо Паскале, занимает весь доступный объем памяти, и вызываемая программа не будет загружена. Полезно включить в текст вызывающей программы директиву компилятора, в которой изменяются принятые по умолчанию размеры памяти. Например, так:
{$М 2048, 0, 0}
Такая директива ограничивает используемую программой область стека величиной 2 Кбайта и исключает возможность использования в ней динамической памяти. Разумеется, Вы можете установить и другие значения параметров в этой директиве.
Специфические особенности исполнения программ Турбо Паскаля требуют изменения стандартных значений некоторых векторов прерываний. К ним относятся векторы со следующими шестнадцатеричными номерами:
$00, $02, $18, $23, $24, $34, $35, $36, $37,
$38, $39, $ЗА, $ЗВ, $ЗС, $3D, $3E, $3F, $75.
Начальные значения этих векторов сохраняются в восемнадцати переменных с именами SA VEINTXX из библиотечного модуля SYSTEM, где XX - шестнадцатеричный номер прерывания. Поэтому непосредственно перед запуском внешней программы и сразу после возврата из нее рекомендуется вызывать библиотечную процедуру без параметров SWAPVECTORS, которая обменивает содержимое векторов прерывания и перечисленных переменных.
Программа из примера 11.4 читает с клавиатуры любую команду ДОС, затем вызывает командный процессор COMMAND. COM операционной системы и передает ему эту команду.
Обратите внимание: для указания файла COMMAND.COM и пути к нему используется обращение к библиотечной функции GETENV, с помощью которой можно получить параметры настройки операционной системы. В частности, параметр COMSPEC определяет спецификацию файла, содержащего командный процессор.
Пример 11.4
{$М 1024, 0, 0}
Uses DOS;
var
st : string [79];
begin
write ('Введите команду ДОС: ');
readln (st); if st <> '' then
begin
st := '/C '+st;
SwapVectors;
Exec (GetEnv ('COMSPEC'), st) ;
SwapVectors
end
end.
Функция ENVCOUNT. Возвращает значение типа INTEGER, в котором содержится общее количество установленных в ДОС параметров. Обращение:
ENVCOUNT
Функция ENVSTR. Возвращает значение типа STRING, содержащее имя и значение нужного параметра настройки операционной системы. Формат обращения:
ENVSTR (<D>)
Здесь <D> - выражение типа INTEGER; номер параметра.
Эта функция возвращает строку типа NAME-VALUE, где NAME - имя, a VALUE -значение соответствующего параметра настройки.
Функция GETENV. Возвращает значение типа STRING, в котором содержится параметр настройки ДОС. Формат обращения:
GETENV (<имя>)
Здесь <имя> - выражение типа STRING; имя параметра.
Эта функция имеет параметр обращения NAME, а возвращает значение VALUE (см. функцию ENVSTR).
Как отмечалось в гл.9, максимальный размер модуля не может превышать 64 Кбайта, однако количество модулей не ограничено, что дает возможность разрабатывать весьма крупные программы, занимающие, например, всю доступную оперативную память ПК (приблизительно 580 Кбайт). Тем не менее, в некоторых случаях и этот объем может оказаться недостаточным. Турбо Паскаль предоставляет в распоряжение программиста простой и достаточно эффективный механизм оверлея, с помощью которого можно создавать программы практически неограниченной длины (следует оговориться, что речь идет только о длине кода программы; два важных размера -длина сегмента данных и размер программного стека - в Турбо Паскале не могут превышать 64 Кбайта независимо от структуры программы).
Оверлей - это такой способ использования оперативной памяти, при котором в один и тот же участок памяти, называемый оверлейным буфером, попеременно по мере надобности загружаются различные оверлейные (перекрывающиеся) модули. При этом все оверлейные модули в готовом к работе виде хранятся на диске, а в оперативной памяти в каждый момент находится лишь один активный модуль и, возможно, небольшое число неактивных.
Пусть, например, программа (рис. 11.1) состоит из главной части MAIN и двух модулей А к В, a LM, LA и LB - соответственно длина главной части и обоих модулей, причем LA > LB. Тогда неоверлейная программа займет в памяти LM + LA + LB байт, в то время как- оверлейная программа - лишь LM + LA байт.
При исполнении оверлейной программы в память первоначально загружается главная часть и один из модулей, например, модуль А. Если в процессе исполнения программы встретится обращение к модулю В, программа приостановит свою работу, с диска в оверлейный буфер будет загружен модуль В (модуль А при этом частично уничтожается), после чего программа продолжит свою работу. Если в дальнейшем встретится обращение к А, точно таким же образом будет загружен модуль А, причем загрузка нужных модулей в оверлейный буфер осуществляется автоматически и программисту не нужно об этом заботиться.
Puc.11.1. Пример структуры программы: а) неоверлейная; 6) оверлейная
Описанный механизм выявляет главное преимущество оверлейной структуры: объем оперативной памяти, занимаемой оверлейной программой, определяется длиной ее главной части и наибольшего из перекрывающихся модулей, в то время как при неоверлейной структуре в этот объем входит суммарная длина всех модулей. Чем больше в программе оверлейных модулей и чем меньше длина наибольшего из них, тем больший выигрыш в памяти дает оверлейная структура. Однако совершенно очевиден и главный недостаток таких структур: на каждую-загрузку оверлейного модуля с диска в оверлейный буфер требуется дополнительное время, поэтому оверлейная программа будет исполняться с меньшей скоростью.
Работа оверлейных программ обеспечивается с помощью процедур и функций библиотечного модуля OVERLAY, входящего в библиотечный файл TURBO.TPL.
При создании оверлейных программ нужно руководствоваться следующей последовательностью действий.
Program Main;
Uses CRT, DOS,Graph, Overlay, UnitA, UnitB;
{$0 DOS}
{$0 UnitA}
{$O UnitB}
Следует подчеркнуть, что из всех стандартных библиотечных модулей только один модуль DOS может быть оверлейным, остальные модули (CRT, Graph, Printer и т.д.) не могут объявляться оверлейными.
Таким образом, все процедуры и функции в оверлейной программе должны использовать дальнюю модель вызова - это обязательное условие. Отметим, что попытка компиляции оверлейного модуля, в начале которого отсутствует директива {$О+} предполагается, что опция среды OPTIONS/COMPILE/OVERLAY ALLOWED неактивна), будет обнаружена компилятором, в то время как! неправильная (ближняя) модель вызова оверлейных подпрограмм компилятором не контролируется и может привести к непредсказуемым результатам при исполнении программы.
Далее, инициация оверлея (осуществляется вызовом процедуры OVRINIT, см. ниже) должна происходить до вызова любого из оверлейных модулей. Это требование кажется тривиальным, однако множество проблем в оверлейных программах обычно :вязано именно с ним. Дело в том, что обращение к оверлейному модулю может происходить еще до начала работы основной программы: напомню, что любой модуль (в том числе и оверлейный) может иметь инициирующую часть, которая исполняется перед началом работы основной программы. В связи с этим рекомендую придерживаться следующего простого правила: никогда не используйте оператор BEGIN в конце модуля, если Вам нет нужды в инициирующих действиях; пустая инициирующая часть содержит пустой оператор, которому будет передано управление на этапе инициации. Таким образом, пустая инициирующая часть оверлейного модуля очень часто может вызывать сообщение об ошибке периода исполнения с кодом 208 (не установлена система управления оверлеем). Как же быть, если в оверлейном модуле все-таки нужна инициирующая часть? В этом случае можно рекомендовать следующий прием. Создайте лишний модуль, в котором будут пустыми все части, кроме инициирующей. В этой части разместите команды инициации оверлея. Новый модуль не должен быть оверлейным и его имя должно стоять в предложении USES основной программы перед именем любого оверлейного модуля. После компиляции такой программы инициация оверлея будет осуществляться перед выполнением любой другой инициирующей части и проблема будет решена.
Процедура OVRINIT. Инициализирует оверлейный файл. Обращение:
OVRINIT (<имя>)
Здесь <имя> - выражение типа STRING; имя файла с оверлейной частью программы.
При компиляции оверлейной программы создается специальный файл с именем, совпадающим с именем главной программы, и расширением .OVR. В этот файл компилятор помещает все оверлейные модули, из него же эти модули загружаются в оверлейный буфер в процессе исполнения программы. Файл с оверлейной частью программы должен размещаться в том же каталоге, что и файл с главной частью (с расширением .ЕХЕ). Отметим, что имя оверлейного файла необходимо дополнять расширением .OVR.
Обычно размер оверлейного буфера определяется автоматически таким образом, чтобы в нем мог разместиться самый крупный из всех оверлейных модулей. Программист может увеличить размер буфера. Тогда при загрузке в буфер очередного модуля программа проверит, достаточно ли в буфере свободного места и, если достаточно, загрузит новый модуль сразу за старым, который, таким образом, не будет уничтожен. Такой механизм способствует минимизации потерь времени на перезагрузку модулей. Если установлен очень большой размер буфера, то в нем, возможно, смогут разместиться все оверлейные модули, однако в этом случае оверлейная структура становится просто ненужной.
Процедура OVRSETBUF. Устанавливает размер оверлейного буфера. Формат обращения:
OVRSETBUF (<длина>)
Здесь <длина> - выражение типа LONGINT задает новую длину буфера в байтах не больше той, которую устанавливает сама система автоматически. Расширение буфера идет за счет соответствующего уменьшения доступной динамической памяти, поэтому к моменту вызова этой процедуры куча должна быть пустой.
Функция OVRGETBUF. Возвращает значение типа LONGINT, содержащее текущий размер оверлейного буфера. Обращение:
OVRGETBUF
Процедура OVRINITEMS. Обеспечивает использование расширенной памяти. Если Ваш ПК относится к классу компьютеров типа IBM PC/AT и в нем имеется так называемая EMS-памятъ (Expanded Memory Specification - расширенная1 память, удовлетворяющая стандарту фирм Lotus/Intel/Microsoff). Вы можете использовать эту память для размещения в ней оверлейного файла .OVR. Поскольку время доступа к расширенной памяти значительно меньше времени чтения с диска, такое размещение увеличивает скорость исполнения оверлейной программы. Обращение:
OVRINITEMS
При обращении к этой процедуре программа прежде всего проверит, достаточен ли объем имеющейся в Вашем ПК EMS-памяти для размещения оверлейного файла. Если это так, то оверлейный файл считывается в EMS-память, сам файл закрывается, и программа будет получать оверлейные модули из этой памяти. Если же ЕMS-память отсутствует или ее объем недостаточен, обращение к процедуре игнорируется, и программа будет считывать оверлейные модули с диска.
Все управление оверлеем осуществляется стандартной подпрограммой, которая называется администратором оверлея. Эта подпрограмма получает управление всякий раз, когда программа обращается к ресурсам оверлейного модуля, не размещенного в данный момент в буфере. Администратор оверлея сначала перемещает предыдущий оверлейный модуль из буфера в так называемую контрольную зону, а уже затем грузит с диска в буфер новый модуль. Если в момент, когда оверлей находится в контрольной зоне, программа вновь обратится к нему, он вернется на свое старое место и таким образом затраты времени на обмен данными с диском будут уменьшены. Программист может задать размер контрольной зоны с помощью обращения к процедуре OVRSETRETRY и получить этот размер с помощью функции OVRGETRETRY. Обычно размер контрольной зоны составляет от одной трети до половины размера оверлейного буфера. Вы можете подобрать этот размер экспериментально в ходе пробного прогона программы. Для этого используются две переменные модуля OVERLAY:
var
OvrTrapCount: Word;{Счетчик обращений ,к администратору}
OvrLoadCount: Word;{Счетчик загрузок в оверлейный буфер}
Всякое обращение программы к оверлейному модулю, которого нет в оверлейном буфере (в том числе и к модулю, находящемуся в контрольной зоне), приводит к наращиванию содержимого переменной OVRTRAPCOUNT на единицу. Всякая загрузка оверлейного модуля из файла в буфер увеличивает на единицу счетчик OVRLOADCOUNT.
В интерфейсной части модуля ОVERLAY объявлены еще три переменные, которые могут оказаться полезными для некоторых применений.
type
vrReadFunc = Function (OvrSeg: Word): Integer;
var
vrReadBuf : OvrReadFunc;{Функция чтения из оверлейного файла}
vrResult :Integer;{Признак ошибки оверлея}
vrFileMode:Byte;{Способ доступа к оверлейному файлу}
Переменная OVRRESULT содержит код, указывающий на успех или неуспех каждой очередной операции администратора оверлея. Значения этой переменной могут быть такими:.
0 - операция прошла успешно;
-1 - общая ошибка;
-2 - не найден оверлейный файл;
-3 - не хватает памяти для оверлейного буфера;
-4 - ошибка чтения-записи оверлейного файла;
-5 - не работает драйвер EMS-памяти;
-6 - не хватает EMS-памяти.
Переменная OVRFILEMODE обычно содержит 0, что трактуется как возможность доступа к оверлейному файлу только для чтения информации. Перед вызовом процедуры OVRINIT программа может установить другое значение этой переменной и таким образом изменить доступ к файлу, что бывает необходимым, если ПК подключен к сети ЭВМ.
В переменной OVRREADBUF содержится имя функции, к которой обращается администратор оверлея при каждом чтении из оверлейного файла. Программа может перехватить обращение к этой функции и проанализировать результат операции. Для этого необходимо в основной программе сохранить имя стандартной функции чтения в глобальной переменной типа OVRREADFUNC и поместить в переменную OVRREADBUF имя новой функции. В эту новую функцию администратор будет передавать управление всякий раз, когда появится необходимость чтения из оверлейного файла. Программа может проверить состояние дисков перед исполнением операции (например, наличие нужного сменного диска), выполнить саму операцию (путем вызова функции, сохраненной в глобальной переменной), проверить результат обращения и предпринять необходимые действия. Отметим, что нормальное завершение операции чтения указывается нулевым значением функции чтения, ненулевое значение означает ту или иную ошибку; код ошибки стандартен для ДОС (см. прил.3) и/или для драйвера EMS-памяти.
При желании Вы можете пристыковать оверлейный файл в конец EXE-файла основной программы. При этом следует учесть, что интегрированная среда пристыковывает в конец EXE-файла отладочные таблицы, поэтому программу и все ее модули следует компилировать в режиме отключенных опций OPTIONS /COMPILER/DEBUG INFORMATION и OPTIONS/COMPILER/LOCAL SYMBOLS (см.прилЛ.2.8). Для объединения EXE-файла с оверлейным файлом необходимо дать такую команду ДОС:
СОРY /В NAME.EXE+NAME.OVR
Здесь NAME.EXE - имя EXE-файла, NAME.OVR - имя оверлейного файла. Чтобы оверлеи читались из ЕXE-файла, нужно просто указать имя этого файла при обращении к OVRINIT:
Ovrlnit(ParamStr(0));
(в программу всегда передается параметр ParamStr(0), в котором ДОС сообщает полное имя запущенной программы - с указанием диска и каталога, откуда была загружена программа).
11.7. ПРЯМОЕ ОБРАЩЕНИЕ К ПАМЯТИ И ПОРТАМ ВВОДА-ВЫВОДА
В Турбо Паскале имеется пять предварительно объявленных массивов: MEM, MEMW, MEML, PORT и PORTW. Первые три обеспечивают доступ к любому участку оперативной памяти по абсолютному адресу, два других - доступ к портам ввода-вывода.
Компонентами массива MEM являются данные типа BYTE, массива MEMW - типа WORD, массива MEML - типа LONGINT. Обращение к элементам этих массивов, т.е. их индексация, имеет специальный вид: каждый индекс представляет собой абсолютный адрес и состоит из двух выражений типа WORD; первое дает сегментную часть адреса, второе - смещение; выражения разделяются двоеточием. Например:
Меm[$0000:$1000] := 0;
DataMem := MemWfSeg(p):0fs(p)];
MemLong :.= MemL [64 : i*SizeOf (real) ] ;
Как следует из технического описания операционной системы MS-DOS, в памяти по адресу $F000:$FFFE располагается байт-указатель типа компьютера. Следующая программа (пример 11.5) прочтет этот байт и выведет на экран тип Вашего ПК.
Пример 11.5
begin
Write (' Тип компьютера: ') ;
case Mem [$FOOO:$FFFE] of
$FF : WriteLn ('PC');
$FE : WriteLn('XT');
$FD : WriteLn('PCjr');
$FC : WriteLn('AT');
$F9 : WriteLn('совместимый с PC')
end
end.
Компонентами массива PORT являются байты (тип BYTE), а массива PORTW - слова (тип WORD). Индексами этих массивов должно быть выражение типа BYTE, указывающее номер нужного порта. Присвоение значения элементу массива PORT или PORTW приведет к записи в порт, упоминание элемента в выражении - к чтению из порта. Компоненты массивов PORT и PORTW нельзя передавать в качестве параметров процедурам или функциям. Эти идентификаторы не употребляются без индексных выражений.
Среда Турбо Паскаль 7.0 обладает весьма интересным новшеством: в ней введена поддержка длинных строк, т.е. строк, длина которых может превышать 255 байт. Как известно, тип String в Турбо Паскале имеет максимальную длину 255 байт. Это связано с тем, что истинная длина строки в этом типе указывается первым байтом, мощность которого не позволяет определять строки большей длины. В то же время в языке С используется другой подход к заданию текстовых строк: первый байт строки является ее первым символом, второй байт - вторым символом и т.д. пока не встретится байт с символом #0. Таким образом, длина строки не указывается явно, как в типе String, а определяется по замыкающему символу #0. Ясно, что такие строки могут иметь произвольную длину, ограничиваемую лишь объемом оперативной памяти или принятой схемой ее адресации: в MS-DOS такой границей является длина сегмента, поэтому максимально возможная длина С-строки для ПК составляет 65535 символов. Такие строки в дальнейшем будем называть ASCIIZ-строками.
Для реализации операций над ASCIIZ-строками в язык введен новый тип PChar, определяемый как указатель на символ:
type
PChar =^Char;
Однако такой обычный для Паскаля тип-указатель в рамках Турбо Паскаля 7.0 трактуется необычным способом: считается, что он указывает на цепочку символов, заканчивающуюся терминальным нулем, т.е. на ASCIIZ-строку. Более того, с этим типом совместим любой одномерный символьный массив с нулевой левой границей, а переменные типа PChar можно индексировать, как если бы они были массивами символов. Следующая программа напечатает все заглавные буквы английского алфавита:
{$Х+} {Включаем расширенный синтаксис}
var
Chars : array [0..26] of Char; {Массив символов}
PChars: PChar; {Указатель на символ}
k : Integer;
begin
for k := 0 to 25 do
Chars[k] := Chr(k+ord('A'); {Наполняем массив}
PChars := Chars; {Указателю присваиваем массив!}
PChars[26] := #0; {Индексируем указатель!}
WriteLn(PChars) {Печатаем указатель!}
end.
Три последние оператора программы недопустимы в стандартном Паскале и в ранних версиях Турбо Паскаля, но возможны в версии 7.0, если включен расширенный синтаксис (директивой {$Х+} или опцией Options/Compiler/Extended syntax). Обратите внимание: процедура WriteLn этой версии умеет работать с ASCIIZ-строками.
Для поддержки ASCIIZ-строк разработан модуль Strings, в котором реализованы необходимы процедуры и функции.
Функция StrCat. Объединяет строки. Заголовок:
Function StrCat(Dest, Source: PChar): PChar;
Копирует строку Source в конец строки Dest и возвращает указатель на начало Dest.
Функция StrComp. Сравнивает строки. Заголовок:
Function StrComp(Strl, Str2: PChar): Integer;
Побайтно сравнивает строку Strl со строкой Strl и возвращает следующий результат:
=0 Strl=Str2;
>0 Strl>Str2;
<0 Strl<Str2.
Функция StrCopy. Копирует строку. Заголовок:
Function StrCopy(Dest, Source: PChar): PChar;
Копирует строку Source в строку Dest и возвращает указатель на Dest. StrCopy не проверяет реальный размер памяти, связанный с Dest (он должен быть не меньше StrLen(Source)+1).
Процедура StrDispose. Удаляет строку из кучи. Заголовок:
Procedure StrDispose(Str: PChar);
Строка Str должна быть предварительно помещена в кучу функцией StrNew. Если Str=NIL, процедура ничего не делает.
Функция StrECopy. Объединяет строки. Заголовок:
Function StrECopy(Dest, Source: PChar): PChar;
Эта функция работает в точности как StrCat, но возвращает указатель на конец сцепленных строк, т.е. на терминальный ноль.
Функция StrEnd. Возвращает конец строки. Заголовок:
Function StrEnd(Str: PChar): PChar;
Функция возвращает указатель на терминальный ноль ASCIIZ-строки Str.
Функция StrlComp. Сравнивает строки. Заголовок:
Function StrlComp(Strl, Str2: PChar): PChar; .
Функция сравнивает строки, игнорируя возможную разницу в высоте букв. Возвращает такой же результат, как и StrComp. Замечу, что функция правильно работает лишь с латиницей. Для кириллицы ее нужно модифицировать (см. ниже).
Функция StrLCat. Объединяет строки. Заголовок:
Function StrLCat(Dest, Source: PChar; MaxLen: Word): PChar;
Копирует символы строки Source в конец строки Dest до тех пор, пока не будут скопированы все символы или когда длина сцепленной строки Dest не достигнет MaxLen. Возвращает указатель на сцепленную строку.
В отличие от StrCopy эта функция блокирует возможное переполнение области памяти, связанной с Dest. Обычно в качестве MaxLen используется выражение SizeOf (Dest) -1. Например:
{$Х+}
Uses Strings;
var
S: array [0..9] of Char;
begin
StrCopy(S, 'Turbo');
StrLCat(S, ' ', SizeOf(S)-1);
StrLCat(S, 'Pascal', SizeOf(S)-1);
WriteLn(S) {Напечатает "Turbo Pas"}
end;
Функция StrLComp. Сравнивает строки. Заголовок:
Function StrLComp(Dest, Source: PChar; MaxLen: Word): PChar;
В отличие от StrComp сравнивает не более MaxLen символов строк. Возвращаемый результат такой же, как и у StrComp.
Функция StrLCopy. Заголовок:
Function StrLCopy(Dest, Source: PChar; MaxLen: Word): PChar;
Копирует символы из строки Source в строку Dest до тех пор, пока не будет скопирована вся строка или пока не будет скопировано MaxLen символов. В отличие от StrCopy блокирует возможное переполнение области памяти, связанной с Dest. В качестве MaxLen обычно используется выражение SizeOf (Dest) -1. Например:
{$Х+}
Uses Strings;
var
S: array [0..9] of Char;
begin
StrLCopy(S, 'Turbo Pascal', SizeOf(S)-1);
WriteLn(S) {Напечатает "Turbo Pas"}
end;
Функция StrLen. Возвращает длину строки. Заголовок:
Function StrCattStr: PChar): Word;
Функция StrLIComp. Сравнивает строки с учетом регистра. Заголовок:
Function StrLIComp(Str1, Str2: PChar; MaxLen: Word): PChar;
Сравнивает не более MaxLen символов строк, проверяя точное соответствие высоты букв. Возвращаемый результат см. StrComp. Функция правильно работает только с латиницей.
Функция StrLower. Преобразует в строчные буквы. Заголовок:
Function StrLower(Str: PChar): PChar;
Преобразует заглавные буквы строки Str к строчным и возвращает указатель на результат. Функция правильно работает только с латиницей.
Функция StrMove. Копирует строку. Заголовок:
Function StrMove(Dest, Source: PChar; Count: Word): PChar;
Копирует точно Count символов строки Source в строку Dest и возвращает указатель на результат. Функция игнорирует действительные размеры строк и может выйти за их пределы.
Функция StrNew. Помещает строку в кучу. Заголовок:
Function StrNew(Str: PChar): PChar;
Функция Str Pas. Преобразует ASCHZ-строку в строку String. Заголовок:
Function StrPas(Str: PChar,) : String;
Функция StrPCopy. Преобразует строку String в ASCIIZ-строку. Заголовок:
Function StrPCopy(Str: PChar; S: String): PChar;
Возвращает указатель на Str.
Функция StrPos. Ищет подстроку. Заголовок:
Function StrPos(Strl, Str2: PChar): PChar;
Ишет подстроку Str2 в строке Strl и возвращает указатель на первое вхождение Str2 или NIL, есди подстрока не найдена.
Функция StrRScan. Ищет последний символ. Заголовок:
Function StrRScan(Str: PChar; Ch: Char): PChar;
Ищет символ Ch в строке Str и возвращает указатель на последний обнаруженный символ Ch или NIL, если символ не найден.
Функция StrScan. Ищет первый символ. Заголовок:
Function StrScan(Str: PChar; Ch: Char): PChar;
Ищет символ Ch в строке Str и возвращает указатель на первый обнаруженный символ Ch или NIL, если символ не найден.
Функция StrUpper. Заголовок:
Function StrLower(Str: PChar): PChar;
Преобразует строчные буквы строки Str к заглавным и возвращает указатель на результат. Функция правильно работает только с латиницей.
Четыре функции модуля Strings (StrLower, StrUpper, StrlComp и StrLIComp) используют преобразование высоты букв и работают корректно только для букв латинского алфавита (латиницы). Для русских букв эти функции можно изменить следующим образом:
{Этот модуль содержит модификацию функций стандартного модуля Strings для работы с кириллицей (альтернативный вариант кодировки}
{$Х+}
Unit StringsR;
INTERFACE
Function LoCase(Ch: Char): Char;
Function UpCase(Ch: Char): Char;
Function StrLower(Str: PChar): PChar;
Function StrUpper(Str: PChar): PChar;
Function StrlComp(Strl, Str2: PChar): Integer;
Function StrLIComp(Strl, Str2: PChar; MaxLen: Word): Integer;
IMPLEMENTATION
Uses Strings;
Function LoCase(Ch: Char): Char;
{Преобразует латинскую или русскую букву Ch к строчной}
begin
case Ch of
'A' . .'Z':LoCase:= Chr(ord('a')+ord(Ch)-ord('A'));
'A' . .'П':LoCase:= Chr(ord('a')+ord(Ch)-ord('A'));
'P' . .'Я':LoCase:= Chr(ord('p')+ord(Ch)-ord('P'));
else
LoCase:= Ch
end
end;
{-------------------}
Function UpCase(Ch: Char): Char;
{Преобразует латинскую или русскую букву Ch к заглавной}
begin
case Ch of
'a'..'z': UpCase := Chr(ord('A')+ord(Ch)-ord('a'));
'a'..'n': UpCase := Chr(ord('A')+ord(Ch)-ord('a'));
'p'..'я': UpCase := Chr(ord('P')+ord(Ch)-ord('p'));
else
UpCase:= Ch
end
end;
{-------------}
Function StrLower(Str: PChar): PChar;
{Преобразует латинские и русские буквы строки Str к строчным}
var
k: Word;
begin
for k := 0 to StrLen(Str)-1 do
Str[k]:= LoCase(Str[k]);
StrLower:= Str
end;
{-----------}
Function StrUpper(Str: PChar): PChar;
{Преобразует латинские и русские буквы строки Str к заглавным}
var
k: Word;
begin
for k :=0 to StrLen(Str)-1 do
Str[k]:= UpCase(Str[k]);
StrUpper:= Str
end;
{------------}
Function StrlComp (Str1, Str2 : PChar): Integer;
{Сравнивает две строки, игнорируя возможную разницу в высоте латинских или русских букв}
var
k: Word;
Max: Word;
begin
{Определяем максимальное количество сравниваемых символов как минимум длин строк}
Мах := StrLen(Str1) ;
if StrLen (Str2).<Max then
Max := StrLen(Str2) ;
{Проверяем символы до первого несовпадения.Буквы преобразуем к заглавным}
for k := 0 to Max-1 do
if UpCase(Strl[k])<>UpCase(Str2[k]) then
begin
{Строки не равны}
StrIComp := ord(UpCase(StrlCk]))-ord(UpCase(Str2[k]));
Exit
end;
{Разницы нет - результат зависит от совпадения длин}
StrIComp := StrLen(Str1)-StrLen(Str2)
end;
{--------------}
Function StrLIComp(Str1, Str2: PChar; MaxLen: Word): Integer;
{Сравнивает не более MaxLen символов строк, проверяя точное соответствие высоты букв}
var
k. Max: Word;
begin
{Определяем максимальное количество сравниваемых символов как минимум длин строк и MaxLen}
Мах := MaxLen;
if StrLen(Strl)<Max then Max := StrLen(Strl);
if StrLen(Str2)<Max then Max := StrLen(Str2);
{Проверяем символы до первого несовпадения.Буквы преобразуем к заглавным}
for k := 0 to Max do
if UpCase(Str1 [k])<>UpCase(Str2[k]) then
begin
StrLIComp := ord(UpCase(Str1[k]))-ord(UpCase(Str2[k]));
Exit
end;
{Разницы нет. Если проверено MaxLen символов, строки считаются равными, в противном случае результат зависит от совпадения длин строк}
if Max=MaxLen then
StrLIComp := 0
else
StrLIComp := StrLen(Str1)-StrLen(Str2)
end;
end.
Если Вы будете использовать этот модуль, ссылайтесь на него в предложении Uses после ссылки на стандартный модуль Strings:
Uses Strings, StringsR, ...